如何将 Python 包发布到 PyPI 的综合指南
在本指南中,我将向你展示如何轻松地将包发布到 Python 包索引(PyPI),以便其他人可以安装和使用你的作品。一旦包在 PyPI 上可用,任何人都可以运行 pip install
来安装该包到他们的机器上。这不仅允许任何人将你的代码作为库导入到他们自己的模块中,你还可以通过 PyPI 发布命令行工具
注意:这些说明主要针对 Mac 和 Linux 平台。我认为它们在 Windows 上的差别不大,但由于我不常在 Windows 上进行开发工作,所以不太了解该平台的细节。
代 码
首先,你需要一些代码来发布!我创建了一个最小的项目,你可以用作起点。你可以在这里找到代码。让我们来看看。
首先让我们浏览一下文件夹结构:
├── README.md
├── package_boilerplate
│ ├── __init__.py
│ └── importable.py
├── requirements.txt
├── scripts
│ └── boilerplate-cli
├── setup.py
└── version.py
这些是 Python 包内部的所有文件。首先,我们有一个名为 package_boilerplate
的目录。这个目录包含了我们模块的所有代码。目录外的所有文件都是包本身的元数据,主要用于包发布过程、安装依赖项等。我们将很快介绍其他文件。
README.md
是包含包详细信息的 Markdown 格式文本。这个内容通常会被渲染为 HTML 并在 GitHub 和 PyPI 等地方显示。
如果我们查看 __init__.py
,我们会看到以下内容:
from colored import fg, bg, attr
def main():
print(f"{fg('dark_orange')}{attr('bold')}You called me!{attr('reset')}")
在这里我们做了几件事:首先,我们导入了一个名为 colored
的库。colored
是一个用于给程序终端输出上色的工具。很有趣吧!
基本上,如果调用 main
函数,这个文件将在终端打印一些彩色文本。如果你继续阅读,你将看到我们如何通过 pip
将其发布为命令行工具!
接下来,让我们看看 importable.py
:
from colored import fg, bg, attr
print(f"{fg('orchid')}{attr('bold')}You imported me!{attr('reset')}")
同样,我们只是向终端打印一些彩色文本,但这次我们可以在外部模块中导入这段代码。由于代码不在函数中,一旦文件被导入,文本将立即被打印。
好的,这就是我们程序的逻辑。让我们继续浏览文件夹结构,看看还有什么是发布所需的。
下一个文件是 requirements.txt
。如果你曾经编写过依赖于其他外部 Python 包的模块,那么你可能对这个文件很熟悉。该文件用于告诉 pip
应该安装哪些版本的模块,如果你运行 pip install -r requirements.txt
,它会一次性安装所有模块。Python 包也不例外;如果它们依赖于其他模块,pip
需要知道要安装哪些模块。我们将在 setup.py
中引用这个文件。
接下来,我们有 scripts
目录。目录名称并不重要,也不是必需的,但我更喜欢将内容分开和组织好。在这个文件夹中,我们有一个名为 boilerplate-cli
的文件。注意我们没有给文件添加 .py
后缀,而是使用了 boilerplate-cli
的命名约定,而不是 boilerplate_cli
。这是因为这个文件将作为命令行程序安装到用户的 PATH 中。我们希望用户能够通过运行 boilerplate-cli
来调用程序,而不是 boilerplate_cli.py
,因为这是命令行程序的预期命名约定。
在这个文件中,我们有以下内容:
#!/usr/bin/env python
from package_boilerplate import main
main()
第一行很重要,因为我们必须告诉用户的操作系统这是什么类型的代码,因为它没有文件扩展名。这被称为 Shebang。接下来,我们从 __init__.py
导入了程序的入口点 main
并运行了该函数。
调用这个函数会启动我们的程序!目前它只是在终端打印文本,但它可以是更复杂程序的入口点。我们将在 setup.py
部分引用这个文件,使其作为命令行程序可供用户使用。
这个过程中最重要的部分是 setup.py
文件。它告诉 pip
如何打包该包以及所有的设置和依赖项。
"""Module setup."""
import runpy
from setuptools import setup, find_packages
PACKAGE_NAME = "package-boilerplate"
version_meta = runpy.run_path("./version.py")
VERSION = version_meta["__version__"]
with open("README.md", "r") as fh:
long_description = fh.read()
def parse_requirements(filename):
"""Load requirements from a pip requirements file."""
lineiter = (line.strip() for line in open(filename))
return [line for line in lineiter if line and not line.startswith("#")]
if __name__ == "__main__":
setup(
name=PACKAGE_NAME,
version=VERSION,
packages=find_packages(),
install_requires=parse_requirements("requirements.txt"),
python_requires=">=3.6.3",
scripts=["scripts/boilerplate-cli"],
description="This is a description.",
long_description=long_description,
long_description_content_type="text/markdown",
)
首先,我们导入了一些名为 runpy
和 setuptools
的实用工具。runpy
内置于 Python,但 setuptools
需要安装,我们将在下一节中讨论。
接下来,我们从 version.py
文件中获取 Python 元数据并获得当前版本号 VERSION
。这个文件只是包含你正在发布包的当前版本号。我们将在下一节讨论为什么它被分成一个独立的文件。
接下来,从 README.md
中提取 long_description
。使用 README
中的内容意味着这个描述只需要存在一个地方。
然后,我添加了一个名为 parse_requirements
的辅助函数。Python 的 setup.py
格式期望 install_requires
属性包含该包依赖项的列表。由于维护包含依赖项的 requirements.txt
文件以及在这里的依赖项列表比较麻烦,这个 parse_requirements
函数简单地从 requirements.txt
导入依赖项,这样你只需要在一个地方维护它们。
最后,我们调用 setup
并传入一些值来定义包。指定 packages=find_packages()
的部分指向名为 package_boilerplate
的目录,并由此函数自动发现。name
和 version
用于 PyPI 目录列表,并且也是人们想要安装包时引用的内容。install_requires
从 requirements.txt
文件中提取依赖项列表。python_requires=">=3.6.3"
是用户必须安装的 Python 版本。最后,scripts
是你指定应为用户安装的命令行程序的地方。
你还应该设置其他值,例如描述和作者。你可以在这里阅读有关可用设置值的更多信息。
最后一个文件 version.py
非常小。这个文件有两个用途:首先,它在上面的 setup.py
模块中被导入,用作包版本;其次,版本值存储在 __version__
属性中。推荐这样做,以便可以通过编程方式发现包的版本。
提示:在工作过程中,你可以在发布之前本地安装你的包。这样,你将在提交到 PyPI 之前知道它是否工作。要本地安装包,你可以在包目录中运行 pip install -e .
。
发布你的包
所以你已经编写了包,并且希望发布它!我们快完成了。还有几个步骤可以发布你的包。
首先需要做的是通过运行 pip install twine
安装一个名为 twine
的工具。twine
是一个用于在 PyPI 上发布 Python 包的工具。然后,我们还需要安装 setuptools
,因为我们在设置脚本中使用了它:pip install setuptools
。
现在,我们需要创建一个将要发布的发行包。为此,运行 python setup.py sdist bdist_wheel
,这将创建 build
和 dist
目录。
构建完成后,我们可以使用 twine
检查是否有任何错误或警告。运行 twine check dist/*
检查发行包是否有错误。如果一切顺利,你应该会看到:
Checking distribution dist/package_boilerplate-1.0.0-py3-none-any.whl: Passed
Checking distribution dist/package-boilerplate-1.0.0.tar.gz: Passed
完成后,我们希望将包发布到 PyPI 测试站点。在发布到真实索引之前,我们可以发布到 test.pypi.org 以确保一切正常!为此,我们可以指示 twine
使用不同的存储库,如下所示:twine upload --repository-url https://test.pypi.org/legacy/ dist/*
如果我们现在运行这个命令,我们将看到以下内容:
Enter your username:
看起来我们需要创建一个帐户!所以前往 PyPI 测试站点的注册页面并创建一个帐户。如果你再次运行命令并输入你的用户名和密码,希望你能看到:
Uploading distributions to https://test.pypi.org/legacy/
Uploading package_boilerplate-1.0.0-py3-none-any.whl
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.95k/4.95k [00:00<00:00, 37.2kB/s]
Uploading package-boilerplate-1.0.0.tar.gz
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.11k/4.11k [00:01<00:00, 3.76kB/s]
如果是这样,恭喜你!你已成功发布你的包!
注意:确保你的包名称是唯一的!
剩下的就是将你的包发布到官方的 pypi.org
。为此,你只需要在那里创建另一个帐户,并在不指定不同存储库的情况下发布你的包,如下所示:twine upload dist/*
。
安装你的包
现在你的包已经发布,我打赌你希望安装它看看效果如何!让我们从 PyPI 测试索引安装我们的包:pip install --index-url https://test.pypi.org/simple/ package-boilerplate
。将来,如果我们不提供 --index-url
参数,那么我们将请求官方索引中的包。如果一切顺利,你应该会看到:
Looking in indexes: https://test.pypi.org/simple/
Collecting package-boilerplate
Downloading https://test-files.pythonhosted.org/packages/10/63/208bbd4ea4427f5eb0e4e6248973b387f1dec4ed60333c4daf416c310903/package_boilerplate-1.0.0-py3-none-any.whl
Requirement already satisfied: colored==1.3.93 in /usr/local/lib/python3.7/site-packages (from package-boilerplate) (1.3.93)
Installing collected packages: package-boilerplate
Successfully installed package-boilerplate-1.0.0
如果是这样,那么包已安装!所以让我们使用它。记得我们创建的命令行程序吗?现在我们应该能够通过调用脚本名称从任何地方运行它:
$ boilerplate-cli
文本是橙色的,就像我们想要的一样!如果我们想在另一个项目中导入模块呢?让我们试试:
$ python
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
它工作了!而且我们甚至得到了彩色文本。
一旦你将包发布到官方 PyPI 索引,你将能够通过运行 pip install package-boilerplate
安装该包。当然,你会希望在整个过程中选择一个不同的包名称并在此处使用它。